package org.lex.net.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.log4j.Logger;

/**
 * @author Liu Er Xiao
 * @version 1.0, 2009-2-22
 */
public class HttpSender {
	public enum Method {
		GET, POST, PUT, DELETE
	}

	public static final String DEFAULT_CHARSET = "UTF-8";

	private Logger log = Logger.getLogger(getClass());

	private String url = null;
	private RequestEntity requestEntity = null;
	private Method method = Method.POST;
	private HttpMethod httpMethod = null;

	private int retryTimes = 3;
	private int timeout = 120000;

	protected HttpSender() {
	}

	public SendResult send() throws HttpException, IOException {
		log.debug("Send message to: " + getUrl());
		httpMethod = this.getHttpMethod();
		HttpClient httpClient = new HttpClient();
		httpClient.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, getTimeout());
		httpClient.executeMethod(httpMethod);
		log.info("Response status : " + httpMethod.getStatusLine());

		String charset = getCharset(getRequestEntity());
		log.debug("Sender Charset: " + charset);
		return new SendResult(httpMethod, charset);
	}

	public String getUrl() {
		return url;
	}

	public Method getMethod() {
		return method;
	}

	public RequestEntity getRequestEntity() {
		return requestEntity;
	}

	public int getRetryTimes() {
		return retryTimes;
	}

	public int getTimeout() {
		return timeout;
	}

	private String getCharset(RequestEntity entity) {
		if (null == entity) {
			return DEFAULT_CHARSET;
		}
		String contentType = entity.getContentType();
		int ch = contentType.indexOf(';');
		if (-1 == ch) {
			return DEFAULT_CHARSET;
		} else {
			ch = contentType.indexOf('=', ch);
			return -1 == ch ? DEFAULT_CHARSET : contentType.substring(ch + 1);
		}
	}

	private HttpMethod getHttpMethod() {
		if (null == this.httpMethod) {
			this.httpMethod = this.createMethod(getMethod());
		}
		return httpMethod;
	}

	private HttpMethod createMethod(Method method) {
		HttpMethod httpMethod = null;
		switch (method) {
		case GET:
			httpMethod = new GetMethod(getUrl());
			break;
		case POST:
			PostMethod post = new PostMethod(getUrl());
			post.setRequestEntity(getRequestEntity());
			httpMethod = post;
			break;
		case PUT:
			PutMethod put = new PutMethod(getUrl());
			put.setRequestEntity(getRequestEntity());
			httpMethod = put;
			break;
		case DELETE:
			httpMethod = new DeleteMethod(getUrl());
			break;
		default:
			log.error("Illegal argument: " + method + " not recognized!");
			break;
		}
		httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(getRetryTimes(), false));
		return httpMethod;
	}

	private static String normalizeUrl(String protocol, String ip, int port, String path) {
		StringBuilder buf = new StringBuilder();
		buf.append(protocol);
		buf.append("://");
		buf.append(ip).append(':');
		buf.append(port);
		if (null == path || 0 == path.length())
			path = "/";
		if ('/' != path.charAt(0))
			buf.append('/');
		buf.append(path);
		return buf.toString();
	}

	public static void main(String[] args) {
		String[] pathCases = { null, "", "/", "/a/", "abc", "   \tads\n", "/web/xml" };
		for (String path : pathCases) {
			System.out.println(normalizeUrl("http", "localhost", 8080, path));
		}
	}

	public static Builder builder() {
		return new Builder();
	}

	public static class Builder {
		private HttpSender sender = new HttpSender();

		public Builder setUrl(String url) {
			sender.url = url;
			return this;
		}

		public Builder setMethod(Method method) {
			sender.method = method;
			return this;
		}

		public Builder setRetryTimes(int retryTime) {
			sender.retryTimes = retryTime;
			return this;
		}

		public Builder setTimeout(int timeout) {
			sender.timeout = timeout;
			return this;
		}

		public HttpSender build() {
			HttpSender tmp = sender;
			sender = null;
			return tmp;
		}

		public Builder setRequestEntity(RequestEntity entity) {
			sender.requestEntity = entity;
			return this;
		}

		public Builder setRequestBody(String requestBody, String contentType, String charset) throws UnsupportedEncodingException {
			RequestEntityFactory fac = new RequestEntityFactory();
			fac.setContentType(contentType);
			fac.setCharset(charset);
			sender.requestEntity = fac.createStringEntity(requestBody);
			return this;
		}

		public Builder setRequestBody(String requestBody, String charset) throws UnsupportedEncodingException {
			return this.setRequestBody(requestBody, RequestEntityFactory.DEFAULT_CONTENT_TYPE, charset);
		}

		public Builder setRequestBody(String requestBody) throws UnsupportedEncodingException {
			return this.setRequestBody(requestBody, RequestEntityFactory.DEFAULT_CONTENT_TYPE, RequestEntityFactory.DEFAULT_CHARSET);
		}

		public Builder setRequestHeader(String headerName, String headerValue) {
			sender.getHttpMethod().setRequestHeader(headerName, headerValue);
			return this;
		}

		public Builder setQueryString(NameValuePair[] params) {
			sender.getHttpMethod().setQueryString(params);
			return this;
		}
	}
}
